{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# pandapower WLS State Estimation\n",
    "This is an introduction into the usage of the pandapower state estimation module. It shows how to \n",
    "create measurements for a pandapower network and how to perform a state estimation with the weighted least squares (WLS) algorithm.\n",
    "\n",
    "## Example Network\n",
    "\n",
    "We will be using the reference network from the book \"Power System State Estimation\" by Ali Abur and Antonio Gómez Expósito. \n",
    "It contains 3 buses with connecting lines between buses 1-2, 1-3 and 2-3. 8 measurements of different types enable WLS state estimation.\n",
    "\n",
    "We first create this network in pandapower."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:39.927403Z",
     "start_time": "2025-10-20T12:28:35.154582Z"
    }
   },
   "outputs": [],
   "source": [
    "from pandapower.create import (\n",
    "    create_empty_network,\n",
    "    create_bus,\n",
    "    create_ext_grid,\n",
    "    create_line_from_parameters,\n",
    "    create_measurement\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:40.159989Z",
     "start_time": "2025-10-20T12:28:39.933401Z"
    }
   },
   "outputs": [],
   "source": [
    "net = create_empty_network()\n",
    "\n",
    "b1 = create_bus(net, name=\"bus 1\", vn_kv=1., index=1)\n",
    "b2 = create_bus(net, name=\"bus 2\", vn_kv=1., index=2)\n",
    "b3 = create_bus(net, name=\"bus 3\", vn_kv=1., index=3)\n",
    "\n",
    "create_ext_grid(net, 1)  # set the slack bus to bus 1\n",
    "\n",
    "l1 = create_line_from_parameters(net, 1, 2, 1, r_ohm_per_km=.01, x_ohm_per_km=.03, c_nf_per_km=0., max_i_ka=1)\n",
    "l2 = create_line_from_parameters(net, 1, 3, 1, r_ohm_per_km=.02, x_ohm_per_km=.05, c_nf_per_km=0., max_i_ka=1)\n",
    "l3 = create_line_from_parameters(net, 2, 3, 1, r_ohm_per_km=.03, x_ohm_per_km=.08, c_nf_per_km=0., max_i_ka=1)\n",
    "\n",
    "net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can add our measurements, which are valid for one point in time.\n",
    "\n",
    "We add two voltage magnitude measurements on buses 1 / 2 with voltage magnitude of 1.006 pu / 0.968 pu and a standard deviation of 0.004 pu each:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding Measurements\n",
    "\n",
    "Measurements are defined via the pandapower *create_measurement* function.    \n",
    "The physical properties which can be measured are set with the *type* argument and can be one of the following: \"p\" for active power, \"q\" for reactive power, \"v\" for voltage and \"i\" for electrical current.   \n",
    "The element is set with the *element_type* argument, it can be either \"bus\", \"line\" or \"transformer\".    \n",
    "Power is measured in kW / kVar, voltage in per unit and current in A. Bus power injections are positive if power is generated at the bus and negative if it is consumed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:40.207321Z",
     "start_time": "2025-10-20T12:28:40.169368Z"
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "create_measurement(net, \"v\", \"bus\", 1.006, .004, element=b1)        # V at bus 1\n",
    "create_measurement(net, \"v\", \"bus\", 0.968, .004, element=b2)        # V at bus 2\n",
    "net.measurement"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We add bus injection measurements on bus 2 with P=-501 kW and Q=-286kVar and standard deviations of 10kVA: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:40.332964Z",
     "start_time": "2025-10-20T12:28:40.306962Z"
    }
   },
   "outputs": [],
   "source": [
    "create_measurement(net, \"p\", \"bus\", 0.501, 0.01, element=b2)         # P at bus 2\n",
    "create_measurement(net, \"q\", \"bus\", 0.286, 0.01, element=b2)         # Q at bus 2\n",
    "net.measurement"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we add line measurements for lines 0 and 1, both placed at the side of bus 1. The bus parameter defines the bus at which the line measurement is positioned, the line argument is the index of the line."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:40.809964Z",
     "start_time": "2025-10-20T12:28:40.759991Z"
    }
   },
   "outputs": [],
   "source": [
    "create_measurement(net, \"p\", \"line\", 0.888, 0.008, element=l1, side=\"from\")  # Pline (bus 1 -> bus 2) at bus 1\n",
    "create_measurement(net, \"p\", \"line\", 1.173, 0.008, element=l2, side=\"from\")  # Pline (bus 1 -> bus 3) at bus 1\n",
    "create_measurement(net, \"q\", \"line\", 0.568, 0.008, element=l1, side=\"from\")  # Qline (bus 1 -> bus 2) at bus 1\n",
    "create_measurement(net, \"q\", \"line\", 0.663, 0.008, element=l2, side=\"from\")  # Qline (bus 1 -> bus 3) at bus 1\n",
    "net.measurement"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Performing the State Estimation\n",
    "\n",
    "The measurements are now set. We have to initialize the starting voltage magnitude and voltage angles for the state estimator. In continuous operation, this can be the result of the last state estimation. In our case, we set flat start conditions: 1.0 p.u. for voltage magnitude, 0.0 degree for voltage angles. This is easily done with the parameter \"init\", which we define as \"flat\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now run the state estimation. Afterwards, the result will be stored in the table res_bus_est."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:43.344558Z",
     "start_time": "2025-10-20T12:28:40.979125Z"
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "from pandapower.estimation import estimate\n",
    "\n",
    "success = estimate(net, init='flat')\n",
    "print(success)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Handling of Bad Data\n",
    "\n",
    "The state estimation class allows additionally the removal of bad data, especially single or non-interacting false measurements. For detecting bad data the Chi-squared distribution is used to identify the presence of them. Afterwards follows the largest normalized residual test that identifys the actual measurements which will be removed at the end.\n",
    "\n",
    "To test this function we will add a single false measurement to the network (active power flow of line 1 at bus 2):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:43.441138Z",
     "start_time": "2025-10-20T12:28:43.412856Z"
    }
   },
   "outputs": [],
   "source": [
    "create_measurement(net, \"p\", \"line\", 1.0, 0.008, element=l1, side=\"to\")  # Pline (bus 1 -> bus 2) at bus 2\n",
    "net.measurement"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next step is the call of the largest normalized residual test's wrapper function *remove_bad_data* that handles the removal of the added false measurement, and returns a identication of success of the state estimation. The argument structure of this function is similiar to the *estimate* function (compare above). It only provides further adjustments according to the maximum allowed normalized residual (\"rn_max_threshold\"), and the probability of false required by the  chi-squared test (\"chi2_prob_false\")."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:43.963134Z",
     "start_time": "2025-10-20T12:28:43.609761Z"
    }
   },
   "outputs": [],
   "source": [
    "from pandapower.estimation import remove_bad_data\n",
    "import numpy as np\n",
    "\n",
    "success_rn_max = remove_bad_data(net, init='flat', rn_max_threshold=3.0)\n",
    "print(success_rn_max)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The management of results will be the same like for the *estimate* function (see following section)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Working with Results\n",
    "\n",
    "We can show the voltage magnitude and angles directly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:44.040595Z",
     "start_time": "2025-10-20T12:28:44.032580Z"
    },
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "net.res_bus_est.vm_pu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:44.229418Z",
     "start_time": "2025-10-20T12:28:44.207267Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_bus_est.va_degree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results match exactly with the results from the book: Voltages 0.9996, 0.9742, 0.9439; Voltage angles 0.0, -1.2475, -2.7457). Nice!    \n",
    "Let's look at the bus power injections, which are available in res_bus_est as well"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:44.677946Z",
     "start_time": "2025-10-20T12:28:44.653608Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_bus_est.p_mw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:44.834125Z",
     "start_time": "2025-10-20T12:28:44.819826Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_bus_est.q_mvar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also compare the resulting line power flows with the measurements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:45.226387Z",
     "start_time": "2025-10-20T12:28:45.208678Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_line_est.p_from_mw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T12:28:45.413608Z",
     "start_time": "2025-10-20T12:28:45.392784Z"
    }
   },
   "outputs": [],
   "source": [
    "net.res_line_est.q_from_mvar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Again, this values do match the estimated values from our reference book.\n",
    "This concludes the small tutorial how to perform state estimation with a pandapower network."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
